package cn.hy.dataSync.utils;

import cn.hy.dataSync.config.DataBaseType;
import org.apache.commons.lang3.StringUtils;

import java.math.BigInteger;
import java.util.*;


public final class RangeSplitUtils {

    public static List<String> splitAndWrap(String left,
                                            String right,
                                            int expectSliceNumber,
                                            String columnName,
                                            String quote,
                                            DataBaseType dataBaseType) {
        String[] tempResult = doAsciiStringSplit(left, right, expectSliceNumber);
        return wrapRange(tempResult, columnName, quote, dataBaseType);
    }

    public static List<String> splitAndWrap(long left,
                                            long right,
                                            int expectSliceNumber,
                                            String columnName) {
        long[] tempResult = doLongSplit(left, right, expectSliceNumber);
        return wrapRange(tempResult, columnName);
    }

    public static List<String> splitAndWrap(BigInteger left,
                                            BigInteger right,
                                            int expectSliceNumber,
                                            String columnName) {
        BigInteger[] tempResult = doBigIntegerSplit(left, right, expectSliceNumber);
        return wrapRange(tempResult, columnName);
    }

    public static List<String> wrapRange(long[] rangeResult,
                                         String columnName) {
        String[] rangeStr = new String[rangeResult.length];
        for (int i = 0, len = rangeResult.length; i < len; i++) {
            rangeStr[i] = String.valueOf(rangeResult[i]);
        }
        return wrapRange(rangeStr, columnName, "", null);
    }

    public static List<String> wrapRange(BigInteger[] rangeResult,
                                         String columnName) {
        String[] rangeStr = new String[rangeResult.length];
        for (int i = 0, len = rangeResult.length; i < len; i++) {
            rangeStr[i] = rangeResult[i].toString();
        }
        return wrapRange(rangeStr, columnName, "", null);
    }

    public static List<String> wrapRange(String[] rangeResult,
                                         String columnName,
                                         String quote,
                                         DataBaseType dataBaseType) {
        if (null == rangeResult || rangeResult.length < 2) {
            throw new IllegalArgumentException(String.format(
                    "Parameter rangeResult can not be null and its length can not < 2. detail:rangeResult=[%s].",
                    StringUtils.join(rangeResult, ",")));
        }

        List<String> result = new ArrayList<>();

        if (rangeResult.length == 2) {
            result.add(String.format(" (%s%s%s <= %s AND %s <= %s%s%s) ",
                    quote,
                    quoteConstantValue(rangeResult[0], dataBaseType),
                    quote,
                    columnName,
                    columnName,
                    quote,
                    quoteConstantValue(rangeResult[1], dataBaseType),
                    quote));
        } else {
            for (int i = 0, len = rangeResult.length - 2; i < len; i++) {
                result.add(String.format(" (%s%s%s <= %s AND %s < %s%s%s) ",
                        quote,
                        quoteConstantValue(rangeResult[i], dataBaseType),
                        quote,
                        columnName,
                        columnName,
                        quote,
                        quoteConstantValue(rangeResult[i + 1], dataBaseType),
                        quote));
            }
            result.add(String.format(" (%s%s%s <= %s AND %s <= %s%s%s) ",
                    quote,
                    quoteConstantValue(rangeResult[rangeResult.length - 2], dataBaseType),
                    quote,
                    columnName,
                    columnName,
                    quote,
                    quoteConstantValue(rangeResult[rangeResult.length - 1], dataBaseType),
                    quote));
        }
        return result;
    }

    private static String quoteConstantValue(String aString,
                                             DataBaseType dataBaseType) {
        if (dataBaseType == null) {
            return aString;
        }
        if (dataBaseType.equals(DataBaseType.MySql)) {
            return aString.replace("'", "''").replace("\\", "\\\\");
        } else {
            return aString;
        }
    }

    public static String[] doAsciiStringSplit(String left,
                                              String right,
                                              int expectSliceNumber) {
        int radix = 128;
        BigInteger[] tempResult = doBigIntegerSplit(
                stringToBigInteger(left, radix),
                stringToBigInteger(right, radix),
                expectSliceNumber);
        String[] result = new String[tempResult.length];

        //处理第一个字符串（因为：在转换为数字，再还原的时候，如果首字符刚好是 basic,则不知道应该添加多少个 basic）
        result[0] = left;
        result[tempResult.length - 1] = right;

        for (int i = 1, len = tempResult.length - 1; i < len; i++) {
            result[i] = bigIntegerToString(tempResult[i], radix);
        }

        return result;
    }


    public static long[] doLongSplit(long left,
                                     long right,
                                     int expectSliceNumber) {
        BigInteger[] result = doBigIntegerSplit(BigInteger.valueOf(left),
                BigInteger.valueOf(right), expectSliceNumber);
        long[] returnResult = new long[result.length];
        for (int i = 0, len = result.length; i < len; i++) {
            returnResult[i] = result[i].longValue();
        }
        return returnResult;
    }

    public static BigInteger[] doBigIntegerSplit(BigInteger left,
                                                 BigInteger right,
                                                 int expectSliceNumber) {
        if (expectSliceNumber < 1) {
            throw new IllegalArgumentException(String.format(
                    "切分份数不能小于1. 此处:expectSliceNumber=[%s].", expectSliceNumber));
        }

        if (left == null || right == null) {
            throw new IllegalArgumentException(String.format(
                    "对 BigInteger 进行切分时，其左右区间不能为 null. 此处:left=[%s],right=[%s].", left, right));
        }

        if (left.compareTo(right) == 0) {
            return new BigInteger[]{left, right};
        } else {
            // 调整大小顺序，确保 left < right
            if (left.compareTo(right) > 0) {
                BigInteger temp = left;
                left = right;
                right = temp;
            }

            //left < right
            // 总长度
            BigInteger endAndStartGap = right.subtract(left);

            // 10000/4  == 一个片范围大小
            BigInteger step = endAndStartGap.divide(BigInteger.valueOf(expectSliceNumber));
            // 剩余数量 = remainder(余数)
            BigInteger remainder = endAndStartGap.remainder(BigInteger.valueOf(expectSliceNumber));

            // 这里不能 step.intValue()==0，因为可能溢出
            if (step.compareTo(BigInteger.ZERO) == 0) {
                expectSliceNumber = remainder.intValue();
            }

            BigInteger[] result = new BigInteger[expectSliceNumber + 1];
            result[0] = left;
            result[expectSliceNumber] = right;

            BigInteger lowerBound;
            BigInteger upperBound = left;
            for (int i = 1; i < expectSliceNumber; i++) {
                lowerBound = upperBound;
                upperBound = lowerBound.add(step);
                // 要把余数分出去
                upperBound = upperBound.add((remainder.compareTo(BigInteger.valueOf(i)) >= 0)
                        ? BigInteger.ONE : BigInteger.ZERO);
                result[i] = upperBound;
            }

            return result;
        }
    }

    /**
     * 由于只支持 ascii 码对应字符，所以radix 范围为[1,128]
     */
    public static BigInteger stringToBigInteger(String aString, int radix) {
        if (null == aString) {
            throw new IllegalArgumentException("参数 bigInteger 不能为空.");
        }

        checkIfBetweenRange(radix, 1, 128);

        BigInteger result = BigInteger.ZERO;
        BigInteger radixBigInteger = BigInteger.valueOf(radix);

        int tempChar;
        int k = 0;

        for (int i = aString.length() - 1; i >= 0; i--) {
            tempChar = aString.charAt(i);
            if (tempChar >= 128) {
                throw new IllegalArgumentException(String.format("根据字符串进行切分时仅支持 ASCII 字符串，而字符串:[%s]非 ASCII 字符串.", aString));
            }
            result = result.add(BigInteger.valueOf(tempChar).multiply(radixBigInteger.pow(k)));
            k++;
        }

        return result;
    }

    /**
     * 把BigInteger 转换为 String.注意：radix 和 basic 范围都为[1,128], radix + basic 的范围也必须在[1,128].
     */
    private static String bigIntegerToString(BigInteger bigInteger, int radix) {
        if (null == bigInteger) {
            throw new IllegalArgumentException("参数 bigInteger 不能为空.");
        }

        checkIfBetweenRange(radix, 1, 128);

        StringBuilder resultStringBuilder = new StringBuilder();

        List<Integer> list = new ArrayList<>();
        BigInteger radixBigInteger = BigInteger.valueOf(radix);
        BigInteger currentValue = bigInteger;

        BigInteger quotient = currentValue.divide(radixBigInteger);
        while (quotient.compareTo(BigInteger.ZERO) > 0) {
            list.add(currentValue.remainder(radixBigInteger).intValue());
            currentValue = currentValue.divide(radixBigInteger);
            quotient = currentValue;
        }
        Collections.reverse(list);

        if (list.isEmpty()) {
            list.add(0, bigInteger.remainder(radixBigInteger).intValue());
        }

        Map<Integer, Character> map = new HashMap<>();
        for (int i = 0; i < radix; i++) {
            map.put(i, (char) (i));
        }

        for (Integer aList : list) {
            resultStringBuilder.append(map.get(aList));
        }

        return resultStringBuilder.toString();
    }

    private static void checkIfBetweenRange(int value, int left, int right) {
        if (value < left || value > right) {
            throw new IllegalArgumentException(String.format("parameter can not <[%s] or >[%s].",
                    left, right));
        }
    }
}
